iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 24
2
Mobile Development

Android Architecture Components 學習心得筆記系列 第 24

Day 24 Navigation (二) 概念原理

  • 分享至 

  • xImage
  •  

Navigation (二) 概念原理

來看看 Navigation 是怎麼管理 Fragment 的跳轉和返回的管理, Navigation 主要有三個部分:

  • Navigation Graph : xml 檔,包含所有被管理的 Fragment,起始目標,換頁目標,返回目標。

  • NavHost : 用來展示 destanation 的容器, Navigation 提供了 NavHostFragment 這個類別用來展示 Fragment,通常會加上 app:defaultNaHost="true" 以攔截系統的返回事件。

    <fragment
        android:id="@+id/navHostFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNaHost="true"
        app:navGraph="@navigation/nav_graph_main" />
  • NavController : 用來管理 NavHost 中的導航動作,通常是寫在點擊事件內完成 Fragment 的切換。
  textView.setOnClickListener {

      findNavController().navigate(R.id.action_Fragment1_to_Fragment2)
  }

返回事件

NavHostFragment 裡面加上 app:defaultNaHost="true" 攔截系統的返回事件後,在這個 Activity 的返回鍵事件就統一交給 NavHostFragment 做管理。

回到 nav_graph_main

  <navigation
        android:id="@+id/navigation"
        app:startDestination="@id/blankFragment">
        <fragment
            android:id="@+id/Fragment1"
            android:name="com.guanhong.mvvmpractice.view.navigation.Fragment1"
            tools:layout="@layout/fragment_first" >
            <action
                android:id="@+id/action_Fragment1_to_Fragment2"
                app:destination="@id/Fragment2" />
        </fragment>
        <fragment
            android:id="@+id/Fragment2"
            android:name="com.guanhong.mvvmpractice.view.navigation.Fragment2"
            tools:layout="@layout/fragment_second" />
    </navigation>

當呼叫到 action_Fragment1_to_Fragment2 這個 action,會從 Fragment1 -> Fragment2

這時候按下返回會默認回到這個 action 所在的 Fragment,以這個例子而言就是 Fragment1,

如果不想要使用默認的話,可以加上 popUpTo 來決定按下返回鍵要回到哪個 Fragment。

<action
    android:id="@+id/action_Fragment1_to_Fragment2"
    app:destination="@id/Fragment2"
    app:popUpTo="@id/Fragment3"/>

自定義 NavController

原生的 FragmentTransaction 在切換 Fragment 有兩種方式,分別是

  • replace 每次切換 Fragment 時會重跑一次 onCreate
  • show, hide 只顯示當前要顯示的 Fragment,並把其他的隱藏起來,不調用任何生命週期

但是 Navigation 默認都是用 replace 切換 Fragment,如果想要用 show, hide 來切換,必須要自定義 Navigator

@Navigator.Name("custom_fragment")  // Use as custom tag at navigation.xml
class CustomNavigator(
    private val context: Context,
    private val manager: FragmentManager,
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(
        destination: Destination,
        args: Bundle?,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ): NavDestination? {
        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

        var initialNavigate = false
        val currentFragment = manager.primaryNavigationFragment
        if (currentFragment != null) {
            transaction.detach(currentFragment)
        } else {
            initialNavigate = true
        }

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            val className = destination.className
            fragment = manager.fragmentFactory.instantiate(context.classLoader, className)
            transaction.add(containerId, fragment, tag)
        } else {
            transaction.attach(fragment)
        }

        transaction.setPrimaryNavigationFragment(fragment)
        transaction.setReorderingAllowed(true)
        transaction.commitNow()

        return if (initialNavigate) {
            destination
        } else {
            null
        }
    }
}

把 fragment 標籤換成 custom_fragment
custom_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/custom_graph"
    app:startDestination="@id/blankFragment1">

    <custom_fragment
        android:id="@+id/blankFragment1"
        android:name="com.guanhong.mvvmpractice.view.navigation.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank">
        <action
            android:id="@+id/action_blankFragment1_to_blankFragment2"
            app:destination="@id/blankFragment2" />
    </custom_fragment>
    <custom_fragment
        android:id="@+id/blankFragment2"
        android:name="com.guanhong.mvvmpractice.view.navigation.BlankFragment2"
        android:label="BlankFragment2">

        <action
            android:id="@+id/action_blankFragment2_to_blankFragment1"
            app:destination="@id/blankFragment1" />
    </custom_fragment>
</navigation>

MainActivity

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_na)

        val navController = findNavController(R.id.customFragment)
        
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.customFragment)!!

        val navigator =
            CustomNavigator(this, navHostFragment.childFragmentManager, R.id.customFragment)

        navController.navigatorProvider.addNavigator(navigator)
        navController.setGraph(R.navigation.custom_graph)
    }

這裡只貼出範例,更詳細的可參考 官網

OK 今天了解了 Navigation 的切換概念、返回事件、並完成了自定義的 NavController

有任何問題或講得不清楚的地方歡迎留言和我討論。

更歡迎留言糾正我任何說錯的地方!

下一篇:Navigation(三) 換頁動畫


上一篇
Day 23 Navigation (一) 介紹與基本使用
下一篇
Day 25 Navigation (三) 換頁動畫
系列文
Android Architecture Components 學習心得筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言